#pragma once

#include <glad/glad.h> // 包含glad来获取所有的必须OpenGL头文件
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <map>
#include "Geometry/Block.h"
#include "baseClass/Physical.h"
#include "noise/PerlinNoise.hpp"
#include "ChunkManager.h"
#include "thread/ThreadPool.hpp"
#include "baseClass/GLog.h"
#include "noise/ChunkGenerator.h"



class ChunkManager;

enum  ChunkType
{
    None = 0,
    Plain,
};





class Chunk
{
private:
    int x = 0;
    int y = 0;

    vector<vector<vector<BlockBase* >>> blocks;


    enum ChunkDataState
    {
        None = 0,
        Change,
        GenerateRenderData,
        GenerateRenderDataFinsh,
        GenerateChunksData,
        GenerateChunksDataFinsh,
        NoneChange,
    };

    static int ChunkSizeX;
    static int ChunkSizeY;
    static int ChunkSizeZ;
    
    int GetNeedRenderface(int x, int y, int z);
    BlockMesh * Meshs = nullptr;
    unordered_map<Texture2D * , vector<BlockVertex>>  RenderData;
    ChunkDataState chunkDataState = None;
    ChunkManager * M_ChunkManager = nullptr;
    static ThreadPool ChunkthreadPool;
    ChunkType chunkType = ChunkType::Plain;
    ChunkGenerator::Generator * chunkGenerator = nullptr;

public:
   
    
    Chunk(ChunkManager * Manager);
    Chunk(ChunkManager * Manager,int x, int y);
    Chunk(ChunkManager * Manager,int x, int y,ChunkType type);
    virtual ~Chunk();


    // TODO:加载与卸载策略
    // void LoadData(vector<uint8_t> &data);
    // vector<uint8_t> GetSaveData();

    //TODO:跨区块选择
    unordered_map<Texture2D * , vector<BlockVertex>> GetRenderData(bool SmoothLighting = false);

    void Render(Blockshader &shader);
    

    template<typename T>
    void SetBlock(int x, int y, int z);

    template<typename T>
    void SetBlocks(int x1,int y1,int x2,int y2,int z1,int z2);
    
    BlockBase * GetBlock(int x, int y, int z);
  

    void SetBrightnessLevel(int x, int y, int z, uint8_t brightnessLevel,int face);

    void UpdataLightIntensity();

    void GenerateChunk();

    void SetRenderDataChange(){
        
        if(chunkDataState == ChunkDataState::None || chunkDataState == ChunkDataState::NoneChange)
        {
            chunkDataState = ChunkDataState::Change;
        }
        
    }

    ChunkDataState GetChunkDataState(){return this->chunkDataState;}
    

    static glm::ivec2 GetGhunkID(glm::ivec2 RealWorldPos)
    {
        int x = RealWorldPos.x / ChunkSizeX;
        if(RealWorldPos.x < 0)
        {
            x =(RealWorldPos.x +1)/16 - 1;
        }
        int y = RealWorldPos.y / ChunkSizeY;
        if(RealWorldPos.y < 0)
        {
            y = (RealWorldPos.y +1)/16 - 1;
        }
        return glm::ivec2(x,y);
    }

    static glm::ivec3 GetGhunkBlockPos(glm::ivec3 RealWorldPos)
    {
        int x,y,z;
        z = RealWorldPos.z;
        if(RealWorldPos.x>=0)
            x = RealWorldPos.x % ChunkSizeX;
        else 
        {
            x = (RealWorldPos.x+1) % ChunkSizeX;
            x = ChunkSizeX+x-1;
        }

        if(RealWorldPos.y>=0)
            y = RealWorldPos.y % ChunkSizeY;
        else 
        {
            y = (RealWorldPos.y+1) % ChunkSizeY;
            y = ChunkSizeY+y-1;
        }

            
        return glm::ivec3(x,y,z);
    }

    glm::ivec3 GetRealWorldPos(glm::ivec3 GhunkBlockPos)
    {
        int realx,realy,realz;

        realx = GhunkBlockPos.x+this->x*ChunkSizeX;
        realy = GhunkBlockPos.y+this->y*ChunkSizeY;
        realz = GhunkBlockPos.z;

        return glm::ivec3(realx,realy,realz);
    }

    
};



template<typename T>
void Chunk::SetBlock(int x, int y, int z)
{
    this->SetRenderDataChange();

    if( std::is_same<T, Air_Block>::value)
    {
        if(this->blocks[x][y][z] != nullptr)
        {
            delete this->blocks[x][y][z];
            this->blocks[x][y][z] = nullptr;
        }
        return;
    }
    
    if(this->blocks[x][y][z] == nullptr)
    {
        this->blocks[x][y][z] = new T();
    }
    else
    {
        delete this->blocks[x][y][z];
        this->blocks[x][y][z] = new T();
    }
}

template<typename T>
void Chunk::SetBlocks(int x1,int y1,int x2,int y2,int z1,int z2)
{
    this->SetRenderDataChange();
    if(x1 < 0 || x2 >= ChunkSizeX || y1 < 0 || y2 >= ChunkSizeY || z1 < 0 || z2 >= ChunkSizeZ)
    {
        ERROR("set block out of range");
 
        return;
    }

    if(x1 > x2) swap(x1, x2);
    if(y1 > y2) swap(y1, y2);
    if(z1 > z2) swap(z1, z2);

    for (int x = x1; x <= x2; x++)
    {
        for (int y = y1; y <= y2; y++)
        {
            for (int z = z1; z <= z2; z++)
            {
                SetBlock<T>(x, y, z);
            }
        }
    }
}